﻿using NAudio.Wave;
using System;
using System.Text;

namespace microp11.rf.tx141bv2
{
    class TempDecoder : IDisposable
    {
        private WaveFileWriter wavFileWriter;
        private WaveIn refWaveIn;
        private WaveBlock accumulator;
        private WaveBlock prevBlock;
        private StringBuilder result;
        private bool saveToFile;

        private byte hiLength;
        private short prevSample;
        private int preambleCount = 0;
        private bool isData = false;
        private int loLength = 0;
        private StringBuilder binaryStream = new StringBuilder();
        private int bitCount = 0;
        private bool extraBitFollow = false;

        private byte[] binaryData = new byte[4];
        private int counter = 0;
        private int count = 0;

        private const int DataLength = 32;
        private const double INVALID_TEMP = -9999.0;
        private byte CRC_C1;
        private byte CRC_C2;
        private int CRC_learned;
        private const int CRC_Threshold = 5;

        public bool SaveToFile
        {
            get { return saveToFile; }
            set { saveToFile = value; }
        }

        public TempDecoder(WaveIn waveIn)
        {
            refWaveIn = waveIn;
            accumulator = new WaveBlock();
            prevBlock = new WaveBlock();
            result = new StringBuilder();

            CRC_C1 = 0;
            CRC_C2 = 0;
            CRC_learned = 0;
        }

        /// <summary>
        /// Data or no data is determined based on the histogram threshold.
        /// Discards consecutive packets containing no data.
        /// Accumulates consecutive packets containing data.
        /// A packet containing no data following a packet containing data, signals end of accumulation and triggers a temp reading. 
        /// </summary>
        /// <param name="block"></param>
        public void Input(WaveBlock block)
        {
            if (block.Threshold > Consts.THRESHOLD)
            {
                accumulator.Samples.AddRange(prevBlock.Samples);
                prevBlock.Clear();

                accumulator.Samples.AddRange(block.Samples);
            }
            else
            {
                //store block to prepend the accumulator if needed
                prevBlock.Clear();
                prevBlock.Samples.AddRange(block.Samples);

                //incoming has no data, process or clear the accumulated
                if (accumulator.Threshold > Consts.THRESHOLD)
                {
                    //add the last received block to accumulator and read temperature
                    accumulator.Samples.AddRange(prevBlock.Samples);
                    prevBlock.Clear();

                    OutputToFile();

                    //destructive reading
                    ReadTemperature();

                    //clear
                    accumulator.Clear();
                }
                else
                {
                    accumulator.Clear();
                    result.Clear();
                }
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        private void OutputToFile()
        {
            if (saveToFile)
            {
                string path = String.Format("{0}{1:dd.HH.mm.ss.ffff}.wav", @"d:\thermometer1\", DateTime.Now);
                wavFileWriter = new WaveFileWriter(path, refWaveIn.WaveFormat);
                wavFileWriter.WriteSamples(accumulator.Samples.ToArray(), 0, accumulator.Samples.Count);
                wavFileWriter.Close();
                wavFileWriter.Dispose();
            }
        }

        public string Output
        {
            get
            {
                return result.ToString();
            }
        }

        private void ReadTemperature()
        {
            count++;
            
            // if an accumulated packet does not have at least 4800 * 4 samples, ignore
            if (accumulator.Size < 4800 * 4)
            {
                result.Append(String.Format("{0:HH:mm:ss.fff}, reading: {1}, samples: {2}, discarded\n", DateTime.Now, count, accumulator.Size));
                return;
            }

            accumulator.HistogramType = Consts.PLUS_HI_HISTOGRAM;
            result.Append(String.Format("{0:HH:mm:ss.fff}, reading: {1}, samples: {2}, max: {3}, coef: {4:0.00}\n", DateTime.Now, count, accumulator.Size, accumulator.MaxValue, accumulator.Threshold));
            result.Append("10-bin Histogram: ");
            for (int i = 0; i < accumulator.Bins.Length; i++)
            {
                result.Append(String.Format("{0, 5}, ", accumulator.Bins[i]));
            }
            result.Remove(result.Length - 2, 2);
            result.Append("\n");

            ClearSampleParser();
            double coef = 0.8;
            for (int i = 0; i < accumulator.Size; i++)
            {
                if (accumulator.Decibels < -10)
                    coef = 0.1;
                else if (accumulator.Decibels < -9)
                    coef = 0.2;
                else if (accumulator.Decibels < -8)
                    coef = 0.3;
                else if (accumulator.Decibels < -6)
                    coef = 0.4;
                else if (accumulator.Decibels < -5)
                    coef = 0.5;
                else if (accumulator.Decibels < -3)
                    coef = 0.6;
                else if (accumulator.Decibels < -1.2)
                    coef = 0.7;

                //separate ones from zeros based on coefficient
                if (accumulator.Samples[i] < coef * 32768)
                    accumulator.Samples[i] = 0;

                //aliasing
                if (i > 1)
                {
                    //010 -> 000
                    if ((accumulator.Samples[i-2] == 0) && (accumulator.Samples[i] == 0))
                    {
                        accumulator.Samples[i-1] = 0;
                    }

                    //101 -> 111
                    if ((accumulator.Samples[i-2] > 0) && (accumulator.Samples[i] > 0))
                    {
                        if (accumulator.Samples[i-1] == 0)
                        {
                            accumulator.Samples[i-1] = (short)(0.5 * (accumulator.Samples[i-2] + accumulator.Samples[i]));
                        }
                    }
                }

                InputSampleParser(accumulator.Samples[i]);
            }

            result.Append("\n");
        }

        private void ClearSampleParser()
        {
            //clear all variables
            hiLength = 0;
            prevSample = 0;
            preambleCount = 0;
            isData = false;
            loLength = 0;
            binaryStream = new StringBuilder();
            bitCount = 0;
            extraBitFollow = false;
            counter = 0;
        }

        /// <summary>
        /// Parser
        /// </summary>
        /// <param name="sample"></param>
        private void InputSampleParser(short sample)
        {
            if (sample != 0)
            {
                hiLength++;
                loLength = 0;
            }
            else
            {
                loLength++;

                if (loLength > 200)
                {
                    isData = false;
                }

                if (prevSample > 0)
                {
                    if (hiLength > 36)
                    {
                        //if data is too short or longer than needed
                        if ((isData) && bitCount > 0)
                        {
                            result.Append("\n");
                        }
                        
                        preambleCount++;
                        hiLength = 0;
                        loLength = 0;
                        isData = false;
                        counter = 0;

                        if (preambleCount % 5 == 0)
                        {
                            if (preambleCount > 5)
                            {
                                binaryStream.Clear();
                            }
                            result.Append(String.Format("{0:HH:mm:ss.fff} {1:D2}: ", DateTime.Now, preambleCount));

                            isData = true;
                            bitCount = 0;
                            extraBitFollow = false;
                        }
                    }
                    else
                    {
                        if (isData)
                        {
                            if (hiLength > 14 && !extraBitFollow)
                            {
                                //we have a ONE
                                bitCount++;
                                result.Append("1");
                                binaryStream.Append("1");

                                if (binaryStream.Length % 4 == 0)
                                    result.Append(" ");
                            }
                            else if (hiLength > 4 && !extraBitFollow)
                            {
                                //we have a ZERO
                                bitCount++;
                                result.Append("0");
                                binaryStream.Append("0");

                                if (binaryStream.Length % 4 == 0)
                                    result.Append(" ");
                            }
                            else
                            {
                                //spurious bits, ignore
                            }

                            hiLength = 0;
                            loLength = 0;

                            if (binaryStream.Length == 8)
                            {
                                ProcessByte(binaryStream);
                                binaryStream.Clear();
                            }

                            if (bitCount == 32)
                            {
                                bitCount = 0;

                                double temp = DecodeTemp(binaryData);

                                if (temp == INVALID_TEMP)
                                {
                                    result.Append(String.Format(" {0:X2}{1:X2}{2:X2}{3:X2} BAD CRC\n",
                                                    binaryData[0], binaryData[1], binaryData[2], binaryData[3]));
                                }
                                else
                                {
                                    if (CRC_learned > CRC_Threshold)
                                    {
                                        result.Append(String.Format(" {0:X2}{1:X2}{2:X2}{3:X2} {4:00.0°C} CRC: {5}\n",
                                                        binaryData[0], binaryData[1], binaryData[2], binaryData[3],
                                                        temp, "OK"));
                                    }
                                    else
                                    {
                                        result.Append(String.Format(" {0:X2}{1:X2}{2:X2}{3:X2} {4:00.0°C} CRC: {5}\n",
                                                        binaryData[0], binaryData[1], binaryData[2], binaryData[3],
                                                        temp, "learning"));
                                    }
                                }
                                extraBitFollow = true;
                            }
                        }
                        else
                        {
                            preambleCount = 0;
                            hiLength = 0;
                        }
                    }
                }
            }

            prevSample = sample;
        }

        private double DecodeTemp(byte[] binaryData)
        {
            int result = 0x0000;
            byte b1 = (byte)(binaryData[1] & 0x03);
            result = result | (b1 << 8);
            result = result | binaryData[2];

            double temp = (result - 500) / 10.0;

            if (IsLearningCRC(binaryData))
            {
                return temp;
            }
            else if (CRC(binaryData) == binaryData[3])
            {
                return temp;
            }
            else
            {
                return INVALID_TEMP;
            }
        }

        private bool IsLearningCRC(byte[] binaryData)
        {
            if (CRC_learned > CRC_Threshold)
            {
                return false;
            }

            byte a1 = (byte)((binaryData[2] & 0xF0) >> 4);
            byte b1 = (byte)((binaryData[3] & 0xF0) >> 4);
            byte c1 = (byte)((b1 - a1) & 0x0F);

            byte a2 = (byte)(binaryData[2] & 0x0F);
            byte b2 = (byte)(binaryData[3] & 0x0F);
            byte c2 = (byte)((b2 - a2) & 0x0F);

            if ((CRC_C1 == c1) && (CRC_C2 == c2))
            {
                CRC_learned++;
            }
            else
            {
                CRC_C1 = c1;
                CRC_C2 = c2;
                CRC_learned = 0;
            }

            return (CRC_learned < CRC_Threshold);
        }

        private byte CRC(byte[] binaryData)
        {
            byte a1 = (byte)((binaryData[2] & 0xF0) >> 4);
            byte c1 = (byte)((a1 + CRC_C1) & 0x0F);

            byte a2 = (byte)(binaryData[2] & 0x0F);
            byte c2 = (byte)((a2 + CRC_C2) & 0x0F);

            return (byte)((c1 << 4) | c2);
        }

        private void ProcessByte(StringBuilder binaryStream)
        {
            if (counter == 4)
                counter = 0;

            byte value = 0x00;
            binaryData[counter] = 0x00;

            //the binaryStream always has 8 literal bits, MSB left
            for (int i = 0; i < 8; i++)
            {
                value = 0;
                if (binaryStream[i] == '1')
                    value = 1;

                binaryData[counter] = (byte)(binaryData[counter] | (byte)(value << (7 - i)));
            }

            counter++;
        }

        public void Dispose()
        {
        }
    }
}
